﻿using System;

namespace InfoPathHelpers.Cryptography.Authentication
{
    public class Authenticator
    {
        private const int SALT_INDEX = 2;
        private const char DELIMITER = ':';

        private IdParts _IdParts;
        private string _AuthorisationKey;

        /// <summary>
        /// Creates a new Authenticator object.
        /// </summary>
        public Authenticator()
        {
            _IdParts = IdParts.All;
        }

        /// <summary>
        /// Creates a new Authenticator object and initializes the AuthorisationKey to the supplied authorisationKey.
        /// </summary>
        /// <param name="authorisationKey">The key with which to intialize AuthorisationKey.</param>
        public Authenticator(string authorisationKey)
            : this()
        {
            _AuthorisationKey = authorisationKey;
        }

        /// <summary>
        /// Gets or sets the authorisation key.
        /// </summary>
        public string AuthorisationKey
        {
            get { return _AuthorisationKey; }
            set { _AuthorisationKey = value; }
        }

        /// <summary>
        /// Generates a new AuthorisationKey.
        /// </summary>
        /// <returns>The generated authorisation key.</returns>
        public string GenerateAuthorisationKey()
        {
            string KeyHash = PasswordHashAlgorithm.CreateHash(IdGenerator.GetUniqueId(ref _IdParts));
            // Prepend _IdParts so we know which were successfully used.
            if (_IdParts != IdParts.None)
            {
                _AuthorisationKey = _IdParts.ToString("D") + ":" + KeyHash;
            }
            else
            {
                _AuthorisationKey = string.Empty;
            }

            return _AuthorisationKey;
        }

        /// <summary>
        /// Generates a new AuthorisationKey using the specified salt.
        /// </summary>
        /// <param name="salt"></param>
        /// <returns></returns>
        private string GenerateAuthorisationKey(string salt)
        {
            string KeyHash = PasswordHashAlgorithm.CreateHash(IdGenerator.GetUniqueId(ref _IdParts), salt);
            // Prepend _IdParts so we know which were successfully used.
            if (_IdParts != IdParts.None)
            {
                _AuthorisationKey = _IdParts.ToString("D") + ":" + KeyHash;
            }
            else
            {
                _AuthorisationKey = string.Empty;
            }

            return _AuthorisationKey;
        }

        /// <summary>
        /// Authenticates the provided authorisationKey against the current machine.
        /// </summary>
        /// <param name="authorisationKey">The key to authenticate.</param>
        /// <returns>True if the supplied key matches that generated by this machine; otherwise false.</returns>
        public bool AuthenticateKey(string authorisationKey)
        {
            _AuthorisationKey = authorisationKey;
            return AuthenticateKey();
        }

        /// <summary>
        /// Authenticates AuthorisationKey against the current machine.
        /// </summary>
        /// <returns>True if the supplied key matches that generated by this machine; otherwise false.</returns>
        public bool AuthenticateKey()
        {
            // Parse the IdParts from the AuthorisationKey which 
            // will be the same as those use in the original RequestKey.
            IdParts KeyIdParts = ParseIdParts(_AuthorisationKey);
            _IdParts = KeyIdParts;

            // Generate a new authorisation key to compare with the existing one.
            string Salt = _AuthorisationKey.Split(new Char[] { DELIMITER })[SALT_INDEX];
            string ComparisonKey = GenerateAuthorisationKey(Salt);

            if (_IdParts != KeyIdParts)
            {
                // We weren't able to generate the same key
                // which is an automatic failure.
                return false;
            }

            // Compare the two keys.
            return ComparisonKey.Equals(_AuthorisationKey, StringComparison.Ordinal);
        }

        private IdParts ParseIdParts(string key)
        {
            int IdParts = int.Parse(key.Substring(0, key.IndexOf(":")));
            return (IdParts)Enum.ToObject(typeof(IdParts), IdParts);
        }
    }
}
